/*
 * Copyright 2012 Marsrutai.lt
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package lt.marsrutai.mobile.android.util;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import android.util.Log;

/**
 * Utility class for obtaining and modifying values of bean properties.
 * 
 * @author Justas
 */
public class BeanUtils {

    /**
     * Returns all properties of the provided class.
     * 
     * @param klass
     * @return
     */
    public static List<BeanProperty> getProperties(Class<?> klass) {
        List<BeanProperty> properties = new ArrayList<BeanProperty>();
        
        for (Method method : klass.getMethods()) {
            if (isGetter(method) && method.getDeclaringClass() != Object.class) {
                properties.add(createProperty(klass, method));
            }
        }
        
        return properties;
    }
    
    /**
     * Gets values of the property
     * 
     * @param object
     * @param property
     * @return
     * @throws IllegalArgumentException if property has no getter
     */
    public static <T> T getPropertyValue(Object object, BeanProperty property) {
        Method getter = property.getGetter();
        if (getter == null) {
            throw new IllegalArgumentException("Property '" + property.getName() + "' has no getter");
        }
        
        return ClassUtils.invokeMethod(object, getter);
    }
    
    /**
     * Sets values of the property
     * 
     * @param object
     * @param property
     * @param value
     * @throws IllegalArgumentException if property has no setter
     */
    public static void setPropertyValue(Object object, BeanProperty property, Object value) {
        Method setter = property.getSetter();
        if (setter == null) {
            throw new IllegalArgumentException("Property '" + property.getName() + "' has no setter");
        }
        
        ClassUtils.invokeMethod(object, setter, value);
    }
    
    private static BeanProperty createProperty(Class<?> klass, Method getter) {
        BeanProperty property = new BeanProperty();
        property.setName(resolvePropertyName(getter));
        property.setGetter(getter);
        property.setType(getter.getReturnType());
        property.setSetter(resolveSetter(klass, property.getName(), getter.getReturnType()));
        
        return property;
    }

    private static String resolvePropertyName(Method getter) {
        String tempName = getter.getName().substring(3);
        
        return Character.toLowerCase(tempName.charAt(0)) + tempName.substring(1);
    }
    
    private static boolean isGetter(Method method) {
        return method.getName().startsWith("get") && method.getParameterTypes().length == 0;
    }
    
    private static Method resolveSetter(Class<?> klass, String name, Class<?> type) {
        String setterName = "set" + Character.toUpperCase(name.charAt(0)) + name.substring(1);
        try {
            return klass.getMethod(setterName, type);
        }
        catch (NoSuchMethodException e) {
            Log.w("BeanUtils", e.getMessage());
        }
        return null;
    }

    public static class BeanProperty {
        
        private String name;
        private Method getter;
        private Method setter;
        private Class<?> type;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Method getGetter() {
            return getter;
        }

        public void setGetter(Method getter) {
            this.getter = getter;
        }

        public Method getSetter() {
            return setter;
        }

        public void setSetter(Method setter) {
            this.setter = setter;
        }

        public Class<?> getType() {
            return type;
        }

        public void setType(Class<?> type) {
            this.type = type;
        }
    }
}
